﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

namespace ControlTest
{
	/// <summary>
	/// Button style for HP Experience buttons.
	/// </summary>
	public enum HPButtonStyle
	{
		/// <summary>Prompts the user to directly engage for a transaction. For example: Buy now, Download, Add to Cart, Log in, Register, and Contact.</summary>
		Critical,
		/// <summary>The most visually prominent button on a page. It encourages customers to do one thing next.</summary>
		Primary,
		/// <summary>One of multiple buttons throughout a page.</summary>
		Secondary,
		/// <summary>Use only when the background is HP Blue.</summary>
		WhiteOnBlue
	}

	/// <summary>
	/// A button using the formatting described in HP Experience 2014.
	/// </summary>
	public class HPButton : System.Windows.Forms.Control, IButtonControl
	{
		private const int colorCount = 6;
		private const HPButtonStyle defBtnStyle = HPButtonStyle.Primary;
		private const int defHeight = 26;
		private const int defLRPadding = 15;
		private const int defRadius = 4;
		private static readonly StringFormat defStringFormat = new StringFormat(StringFormatFlags.NoWrap) { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center, HotkeyPrefix = System.Drawing.Text.HotkeyPrefix.Show };
		private static readonly uint[,] palette = new uint[4, colorCount] {
			{ 0xFFD7410B, 0xFFFFFFFF, 0xFFC1401A, 0xFFFFFFFF, 0xFFD9D9D9, 0xFFFFFFFF }, // Critical
			{ 0xFF0096D6, 0xFFFFFFFF, 0xFF006699, 0xFFFFFFFF, 0xFFD9D9D9, 0xFFFFFFFF }, // Primary
			{ 0xFF767676, 0xFFFFFFFF, 0xFF5A5A5A, 0xFFFFFFFF, 0xFFD9D9D9, 0xFFFFFFFF }, // Secondary
			{ 0xFFFFFFFF, 0xFF0096D6, 0xFFFFFFFF, 0xFF006699, 0xFFD9D9D9, 0xFF5E5E5E }  // WhiteOnBlue
		};

		private HPButtonStyle buttonStyle = defBtnStyle;
		private Dictionary<uint, Brush> ctrlBrushes = new Dictionary<uint, Brush>();
		private GraphicsPath focPath;
		private bool keyDown;
		private bool mouseDown;
		private bool mouseTracking;
		private GraphicsPath path;
		private StringFormat strFmt = new StringFormat(defStringFormat);
		private bool useMnemonic = true;

		/// <summary>
		/// Initializes a new instance of the <see cref="HPButton"/> class.
		/// </summary>
		public HPButton()
		{
			base.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint, true);
			base.SetStyle(ControlStyles.StandardClick | ControlStyles.Selectable | ControlStyles.ResizeRedraw , true);
			base.Font = new Font("HP Simplified", 14, FontStyle.Bold, GraphicsUnit.Pixel);
			this.DialogResult = System.Windows.Forms.DialogResult.None;
			this.AutoSize = true;
		}

		/// <summary>
		/// Gets or sets a value that indicates whether the control resizes based on its contents.
		/// </summary>
		/// <value>
		/// <c>true</c> if the control automatically resizes based on its contents; otherwise, <c>false</c>. The default is <c>true</c>.
		/// </value>
		[DefaultValue(true), Browsable(true), EditorBrowsable(EditorBrowsableState.Always), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
		public override bool AutoSize
		{
			get { return base.AutoSize; }
			set { base.AutoSize = value; }
		}

		/// <summary>
		/// Gets or sets the background color for the control.
		/// </summary>
		/// <PermissionSet>
		///   <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
		/// </PermissionSet>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public override Color BackColor
		{
			get { return base.BackColor; }
			set { base.BackColor = value; }
		}

		/// <summary>
		/// Gets or sets the button style.
		/// </summary>
		/// <value>
		/// The button style.
		/// </value>
		[DefaultValue(typeof(HPButtonStyle), "Primary"), Category("Appearance")]
		public HPButtonStyle ButtonStyle
		{
			get { return buttonStyle; }
			set
			{
				if (buttonStyle != value)
				{
					buttonStyle = value;
					this.Refresh();
				}
			}
		}

		/// <summary>
		/// Gets or sets the value returned to the parent form when the button is clicked.
		/// </summary>
		[DefaultValue(typeof(DialogResult), "None"), Category("Behavior")]
		public DialogResult DialogResult { get; set; }

		/// <summary>
		/// Gets or sets the font of the text displayed by the control.
		/// </summary>
		/// <PermissionSet>
		///   <IPermission class="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
		///   <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
		///   <IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence" />
		///   <IPermission class="System.Diagnostics.PerformanceCounterPermission, System, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
		/// </PermissionSet>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public override Font Font
		{
			get { return base.Font; }
			set { base.Font = value; }
		}

		/// <summary>
		/// Gets or sets the foreground color of the control.
		/// </summary>
		/// <PermissionSet>
		///   <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Unrestricted="true" />
		/// </PermissionSet>
		[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public override Color ForeColor
		{
			get { return base.ForeColor; }
			set { base.ForeColor = value; }
		}

		/// <summary>
		/// Gets or sets the text associated with this control.
		/// </summary>
		[SettingsBindable(true), Editor(typeof(System.ComponentModel.Design.MultilineStringEditor), typeof(System.Drawing.Design.UITypeEditor))]
		public override string Text
		{
			get { return base.Text; }
			set { base.Text = value; this.Refresh(); }
		}

		/// <summary>
		/// Gets or sets a value indicating whether the first character that is preceded by an ampersand (&) is used as the mnemonic key of the control.
		/// </summary>
		/// <value>
		/// <c>true</c> if the first character that is preceded by an ampersand (&) is used as the mnemonic key of the control; otherwise, <c>false</c>. The default is <c>true</c>.
		/// </value>
		[DefaultValue(true), Category("Appearance")]
		public bool UseMnemonic
		{
			get { return useMnemonic; }
			set { useMnemonic = value; strFmt.HotkeyPrefix = value ? System.Drawing.Text.HotkeyPrefix.Show : System.Drawing.Text.HotkeyPrefix.None; }
		}

		/// <summary>
		/// Gets the required creation parameters when the control handle is created.
		/// </summary>
		protected override CreateParams CreateParams
		{
			get
			{
				const int WS_EX_CLIENTEDGE = 0x200;
				CreateParams createParams = base.CreateParams;
				createParams.ExStyle &= ~WS_EX_CLIENTEDGE;
				return createParams;
			}
		}

		/// <summary>
		/// Gets the default size of the control.
		/// </summary>
		protected override Size DefaultSize
		{
			get { return new Size(74, 26); }
		}

		private Brush BgBrush
		{
			get { return GetBrush(BgColor); }
		}

		private uint BgColor
		{
			get { return palette[(int)buttonStyle, this.Enabled ? (mouseTracking ? 2 : 0) : 4]; }
		}

		private Brush FgBrush
		{
			get { return GetBrush(FgColor); }
		}

		private uint FgColor
		{
			get { return palette[(int)buttonStyle, this.Enabled ? (mouseTracking ? 3 : 1) : 5]; }
		}

		/// <summary>
		/// Retrieves the size of a rectangular area into which a control can be fitted.
		/// </summary>
		/// <param name="proposedSize">The custom-sized area for a control.</param>
		/// <returns>
		/// An ordered pair of type <see cref="T:System.Drawing.Size" /> representing the width and height of a rectangle.
		/// </returns>
		public override Size GetPreferredSize(Size proposedSize)
		{
			Size textSize = TextRenderer.MeasureText(this.Text, this.Font);
			return new Size(textSize.Width + (HPButton.defLRPadding * 2), this.DefaultSize.Height);
		}

		/// <summary>
		/// Notifies a control that it is the default button so that its appearance and behavior is adjusted accordingly.
		/// </summary>
		/// <param name="value">true if the control should behave as a default button; otherwise false.</param>
		void IButtonControl.NotifyDefault(bool value)
		{
		}

		/// <summary>
		/// Generates a <see cref="E:System.Windows.Forms.Control.Click" /> event for the control.
		/// </summary>
		void IButtonControl.PerformClick()
		{
			OnClick(EventArgs.Empty);
		}

		/// <summary>
		/// Returns a <see cref="System.String" /> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String" /> that represents this instance.
		/// </returns>
		public override string ToString()
		{
			return base.ToString() + ", Text: " + this.Text;
		}

		internal static GraphicsPath CreateHPShape(Rectangle r, bool draw = false)
		{
			float x = r.X - (draw ? 0f : 0.5f);
			float y = r.Y - (draw ? 0f : 0.5f);
			float xw = x + r.Width - (draw ? 1 : 0);
			float yh = y + r.Height - (draw ? 1 : 0);
			float xwr = xw - defRadius;
			float yhr = yh - defRadius;
			float xr = x + defRadius;
			float yr = y + defRadius;
			float r2 = defRadius * 2;
			float xwr2 = xw - r2;
			float yhr2 = yh - r2;

			GraphicsPath p = new GraphicsPath();
			p.StartFigure();

			p.AddLine(x, yr, x, y);
			p.AddLine(x, y, xr, y);

			//Top Edge
			p.AddLine(xr, y, xwr, y);

			//Top Right Corner
			p.AddArc(xwr2, y, r2, r2, 270, 90);

			//Right Edge
			p.AddLine(xw, yr, xw, yhr);

			p.AddLine(xw, yhr, xw, yh);
			p.AddLine(xw, yh, xwr, yh);

			//Bottom Edge
			p.AddLine(xwr, yh, xr, yh);

			//Bottom Left Corner
			p.AddArc(x, yhr2, r2, r2, 90, 90);

			//Left Edge
			p.AddLine(x, yhr, x, yr);

			p.CloseFigure();
			return p;
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.Click" /> event.
		/// </summary>
		/// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
		protected override void OnClick(EventArgs e)
		{
			Form form = base.FindForm();
			if (form != null)
				form.DialogResult = this.DialogResult;
			base.OnClick(e);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.GotFocus" /> event.
		/// </summary>
		/// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
		protected override void OnGotFocus(EventArgs e)
		{
			base.OnGotFocus(e);
			this.Invalidate();
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.KeyDown" /> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Windows.Forms.KeyEventArgs" /> that contains the event data.</param>
		protected override void OnKeyDown(KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space)
			{
				keyDown = true;
				this.Invalidate();
			}
			base.OnKeyDown(e);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.KeyUp" /> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Windows.Forms.KeyEventArgs" /> that contains the event data.</param>
		protected override void OnKeyUp(KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Space || e.KeyCode == Keys.Return)
			{
				OnClick(EventArgs.Empty);
				keyDown = false;
				e.Handled = true;
				this.Invalidate();
			}
			base.OnKeyUp(e);
		}

		/// <summary>
		/// Raises the <see cref="E:Layout" /> event.
		/// </summary>
		/// <param name="e">The <see cref="LayoutEventArgs"/> instance containing the event data.</param>
		protected override void OnLayout(LayoutEventArgs e)
		{
			base.OnResize(e);
			// Rebuild path
			path = CreateHPShape(this.ClientRectangle);
			focPath = CreateHPShape(this.ClientRectangle, true);
			Matrix matrix = new Matrix();
			matrix.Translate(1, 1, MatrixOrder.Append);
			matrix.Scale((this.Width - 2.0f) / (float)this.Width, (this.Height - 2.0f) / (float)this.Height, MatrixOrder.Append);
			focPath.Transform(matrix);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.LostFocus" /> event.
		/// </summary>
		/// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
		protected override void OnLostFocus(EventArgs e)
		{
			base.OnLostFocus(e);
			this.Invalidate();
		}

		/// <summary>
		/// Raises the <see cref="Control.MouseDown"/> event.
		/// </summary>
		/// <param name="e">An <see cref="MouseEventArgs"/> that contains the event data.</param>
		protected override void OnMouseDown(MouseEventArgs e)
		{
			base.OnMouseDown(e);
			mouseDown = true;
			this.Focus();
			this.Invalidate();
		}

		/// <summary>
		/// Raises the <see cref="Control.MouseEnter"/> event.
		/// </summary>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		protected override void OnMouseEnter(EventArgs e)
		{
			base.OnMouseEnter(e);
			mouseTracking = true;
			this.Invalidate();
		}

		/// <summary>
		/// Raises the <see cref="Control.MouseLeave"/> event.
		/// </summary>
		/// <param name="e">An <see cref="EventArgs"/> that contains the event data.</param>
		protected override void OnMouseLeave(EventArgs e)
		{
			base.OnMouseLeave(e);
			mouseTracking = false;
			mouseDown = false;
			this.Invalidate();
		}

		/// <summary>
		/// Raises the <see cref="Control.MouseMove"/> event.
		/// </summary>
		/// <param name="e">An <see cref="MouseEventArgs"/> that contains the event data.</param>
		protected override void OnMouseMove(MouseEventArgs e)
		{
			base.OnMouseMove(e);
		}

		/// <summary>
		/// Raises the <see cref="Control.MouseUp"/> event.
		/// </summary>
		/// <param name="e">An <see cref="MouseEventArgs"/> that contains the event data.</param>
		protected override void OnMouseUp(MouseEventArgs e)
		{
			base.OnMouseUp(e);
			mouseDown = false;
			base.Refresh();
			OnClick(EventArgs.Empty);
		}

		/// <summary>
		/// Raises the <see cref="E:System.Windows.Forms.Control.Paint" /> event.
		/// </summary>
		/// <param name="e">A <see cref="T:System.Windows.Forms.PaintEventArgs" /> that contains the event data.</param>
		protected override void OnPaint(PaintEventArgs e)
		{
			//e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
			if (Application.RenderWithVisualStyles)
				ButtonRenderer.DrawParentBackground(e.Graphics, e.ClipRectangle, this);

			e.Graphics.FillPath(BgBrush, path);
			Rectangle textRect = this.ClientRectangle;
			if (mouseDown || keyDown) textRect.Offset(1, 1);
			e.Graphics.DrawString(this.Text, this.Font, FgBrush, textRect, strFmt);
			if (this.Focused)
			{
				using (Pen p = new Pen(Color.FromArgb(255, 0xD9, 0xD9, 0xD9)) { DashStyle = DashStyle.Dot })
					e.Graphics.DrawPath(p, focPath);
			}
		}

		/// <summary>
		/// Processes a mnemonic character.
		/// </summary>
		/// <param name="charCode">The character to process.</param>
		/// <returns>
		/// true if the character was processed as a mnemonic by the control; otherwise, false.
		/// </returns>
		protected override bool ProcessMnemonic(char charCode)
		{
			if (this.UseMnemonic && this.CanProcessMnemonic() && Control.IsMnemonic(charCode, this.Text))
			{
				this.OnClick(EventArgs.Empty);
				return true;
			}
			return base.ProcessMnemonic(charCode);
		}

		private bool CanProcessMnemonic()
		{
			return (this.Enabled && this.Visible);
		}

		private Brush GetBrush(uint color)
		{
			Brush br;
			if (!ctrlBrushes.TryGetValue(color, out br))
			{
				br = new SolidBrush(Color.FromArgb((int)color));
				ctrlBrushes.Add(color, br);
			}
			return br;
		}
	}
}
